#ifndef _Random_cu_
#define _Random_cu_

#include <stdio.h>
#include <cuda.h>
#include <time.h>
#include <curand.h>
#include <curand_kernel.h>

#include "CUDAMCMLrng.cu"

#define SAFE_PRIMES_FILENAME "safeprimes_base32.txt"
#define SEED (time(NULL)) // random seed
#define HILOS_X_BLOQUE 128

#define USAR_CURAND

struct Random{
#ifdef USAR_CURAND
	curandState* devStates;
	int cantNumeros;
#else
	unsigned long long* state;
	unsigned int* cons;
	int cantNumeros;
#endif
};

#ifdef USAR_CURAND
static __global__ void setup_kernel ( struct Random r, unsigned long seed )
{
    int id = blockIdx.x * blockDim.x + threadIdx.x; 
	if( id < r.cantNumeros)
		curand_init ( seed, id, 0, &(r.devStates[id]));
}
#endif

static struct Random initRandom(int cantHilos){
	
	struct Random r;

#ifdef USAR_CURAND
	//Calculo cantidad de numeros.
	r.cantNumeros = cantHilos;
	
	//Reservo memoria para los estados.
    cudaMalloc ( &r.devStates, cantHilos*sizeof( curandState ));
    
    // Inicio semillas.
	int bloques = cantHilos / HILOS_X_BLOQUE;
	if((cantHilos % HILOS_X_BLOQUE) > 0)
		bloques++;
	dim3 grid(bloques,1);
	dim3 bloque(HILOS_X_BLOQUE,1,1);
    setup_kernel <<< grid, bloque >>> ( r, SEED );
#else

	//Calculo cantidad de numeros.
	r.cantNumeros = cantHilos;
	
	//Reservo memoria.
	unsigned long long* state = new unsigned long long[cantHilos];
	unsigned int* cons = new unsigned int[cantHilos];

	// configuraci�n del generador de n�meros aleatorio
	init_RNG(state, cons, cantHilos, SAFE_PRIMES_FILENAME, (unsigned long long) SEED);
	
	//Transferencia de datos para generar numeros aleatorios.
	cudaMalloc(&(r.state), cantHilos*sizeof(unsigned long long));
	cudaMalloc(&(r.cons), cantHilos*sizeof(unsigned int));
	cudaMemcpy(r.state, state, cantHilos*sizeof(unsigned long long), cudaMemcpyHostToDevice);
	cudaMemcpy(r.cons, cons, cantHilos*sizeof(unsigned int), cudaMemcpyHostToDevice);
	
	delete[] state;
	delete[] cons;
#endif
	return r;
}

__device__ __inline__ float getRandom(struct Random r, int idx){
#ifdef USAR_CURAND
	return curand_uniform( &(r.devStates[idx]));
#else
	return rand_MWC_co(&(r.state[idx]), &(r.cons[idx]));
#endif
}

__device__ __inline__ unsigned int getRandomInt(struct Random r, int idx){
#ifdef USAR_CURAND
	return curand( &(r.devStates[idx]));
#else
	return curand(&(r.state[idx]), &(r.cons[idx]));
#endif
}

static void deleteRandom(struct Random r){
#ifdef USAR_CURAND
	cudaFree(r.devStates);
#else
	cudaFree(r.state);
	cudaFree(r.cons);
#endif
}

#endif
